/*
 * Copyright (c) 2020-2021 imlzw@vip.qq.com jweb.cc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cc.jweb.adai.web.system.generator.controller;

import cc.jweb.adai.web.system.generator.model.GeneratorModel;
import cc.jweb.adai.web.system.generator.model.vo.PermissionVo;
import cc.jweb.adai.web.system.generator.model.vo.TemplateConfig;
import cc.jweb.adai.web.system.generator.service.CodeGenerator;
import cc.jweb.adai.web.system.generator.service.WebServerService;
import cc.jweb.adai.web.system.generator.utils.GeneratorUtils;
import cc.jweb.adai.web.system.log.service.SysLogService;
import cc.jweb.adai.web.system.oper.model.SysOper;
import cc.jweb.adai.web.system.org.model.SysUser;
import cc.jweb.adai.web.system.role.model.SysRole;
import cc.jweb.adai.web.system.role.model.SysRoleOper;
import cc.jweb.adai.web.system.role.model.SysRoleUser;
import cc.jweb.adai.web.system.sys.model.SysLog;
import cc.jweb.adai.web.websocket.service.LogWebSocketService;
import cc.jweb.boot.common.exception.ParameterValidationException;
import cc.jweb.boot.common.lang.Result;
import cc.jweb.boot.controller.JwebController;
import cc.jweb.boot.db.Db;
import cc.jweb.boot.security.annotation.Logical;
import cc.jweb.boot.security.annotation.RequiresPermissions;
import cc.jweb.boot.security.session.account.JwebSecurityAccount;
import cc.jweb.boot.security.utils.JwebSecurityUtils;
import cc.jweb.boot.utils.gson.GsonUtils;
import cc.jweb.boot.utils.lang.IDGenerator;
import cc.jweb.boot.utils.lang.ResponseUtils;
import cc.jweb.boot.utils.lang.StringUtils;
import cc.jweb.boot.utils.lang.collection.MapUtils;
import cc.jweb.boot.web.resource.WebRootResourceFactory;
import cn.hutool.core.util.ZipUtil;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.jfinal.core.NotAction;
import com.jfinal.log.Log;
import com.jfinal.render.FileRender;
import com.jfinal.render.Render;
import com.jfinal.template.source.ISource;
import io.jboot.web.controller.annotation.RequestMapping;
import org.apache.commons.io.FileUtils;

import javax.servlet.ServletOutputStream;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

@RequiresPermissions("system:generator:code:view")
@RequestMapping(value = "/generator/code", viewPath = "/WEB-INF/views/generator/code")
public class GeneratorModelController extends JwebController {
    private static final Log logger = Log.getLog(GeneratorModelController.class);

    public void index() {
        render("index.html");
    }

    /**
     * 编辑页面
     */
    @RequiresPermissions("system:generator:code:edit")
    public void editPage() {
        String id = getPara("generator_id");
        GeneratorModel generatorModel = null;
        if (id != null) {
            generatorModel = GeneratorModel.dao.findById(id);
            if (generatorModel == null) {
                generatorModel = new GeneratorModel();
            }
        }
        if (generatorModel == null) {
            generatorModel = new GeneratorModel();
        }
        setAttr("detail", generatorModel);
        render("edit.html");
    }

    /**
     * 加载分页列表数据
     */
    @RequiresPermissions("system:generator:code:list")
    public void list() {
        Map<String, Object> params = getPageParamsPlus();
        Object[] values = getParaValues("values");
        Result result = new Result(false, "未知异常！");
        result.set("code", 400);
        if (StringUtils.isNotBlank(values)) { // formSelects的回显接口
            params.put("generator_ids", values);
            result.setListData(Db.find(Db.getSqlPara("sys_generator_model.queryPageList", params)));
            result.setSuccess(true);
            result.set("code", 0);
        } else {
            result = Db.paginate("sys_generator_model.queryPageList", "sys_generator_model.count", params);
            result.set("count", result.get(Result.LIST_TOTAL_KEY));
            result.setSuccess(true);
            result.set("code", 0);
        }
        renderJson(result);
    }

    /**
     * 保存用户信息（新增与修改）
     */
    @RequiresPermissions(value = {"system:generator:code:add", "system:generator:code:edit"}, logical = Logical.OR)
    public void save() {
        GeneratorModel columnModel = getColumnModel(GeneratorModel.class);
        GeneratorModel oldModel = null;
        Object id = columnModel.get("generator_id");
        if (id != null) {
            oldModel = GeneratorModel.dao.findById(id);
        }
        if (oldModel != null) { // 编辑
            columnModel.update();
        } else { // 新增
            columnModel.set("create_datetime", new Date());
            columnModel.save();
        }
        SysLogService.service.setSyslog(SysLog.STATUS_SUCCESS, " 保存代码生成器【" + columnModel.getGeneratorName() + "】成功！");
        renderJson(new Result(true, "保存代码生成器信息成功！").set("gid", columnModel.getGeneratorId()));
    }

    /**
     * 删除记录
     */
    @RequiresPermissions("system:generator:code:del")
    public void delete() {
        String[] ids = getParaValues("ids");
        if (ids == null || ids.length <= 0) {
            renderJson(new Result(true, "删除成功！"));
            return;
        }
        boolean b = true;
        for (String id : ids) {
            b = b & GeneratorModel.dao.deleteById(id);
        }
        SysLogService.service.setSyslog(b ? SysLog.STATUS_SUCCESS : SysLog.STATUS_FAILURE, "删除代码生成器【id:" + StringUtils.join(ids, ",") + "】" + (b ? "成功" : "失败") + " !");
        renderJson(new Result(b, b ? "删除成功！" : "删除失败！"));
    }

    /**
     * 预览生成代码
     */
    public void preview() {
        GeneratorModel generatorModel = GeneratorModel.dao.findById(getPara("generator_id"));
//        List fileList = readFileList(new File(CodeGenerator.getCodeOutputPath(generatorModel)), "");
//        setAttr("fileList", fileList);
        setAttr("generatorModel", generatorModel);
        render("preview2.html");
    }

    /**
     * XCE对象的api接口
     *
     * @throws IOException
     */
    public void xceApi() throws IOException {
        if (isParaBlank("generator_id")) {
            renderJson(new Result(false, "代码生成器编号不能为空！"));
            return;
        }
        String oper = getPara("oper");
        if (isParaBlank("oper")) {
            renderJson(new Result(false, "未知操作！"));
            return;
        }
        GeneratorModel generatorModel = GeneratorModel.dao.findById(getPara("generator_id"));
        if (generatorModel == null) {
            renderJson(new Result(false, "代码生成器不存在！"));
            return;
        }
        switch (oper) {
            case "list":
                listFile(generatorModel);
                break;
            case "content":
                download(generatorModel);
                break;

        }
    }

    /**
     * 文件列表
     */
    @NotAction
    public void listFile(GeneratorModel generatorModel) {
        List fileList = readFileList(new File(CodeGenerator.getTemplateOutputFilePath(generatorModel)), "");
//        Result result = new Result(true);
//        result.setData(fileList);
        renderJson("{ \"success\": true, \"data\": " + (GsonUtils.get().toJson(fileList)) + "}");
    }

    /**
     * 递归加载文件列表
     *
     * @param codePath
     * @return
     */
    @NotAction
    private List readFileList(File codePath, String parentPath) {
        List list = new ArrayList();
        File[] files = codePath.listFiles();
        if (files != null) {
            for (File file : files) {
                String filePath = parentPath + "/" + file.getName();
                Map<String, Object> fileInfo = MapUtils.of("title", file.getName(),
                        "spread", file.isDirectory(),
                        "path", filePath,
                        "id", filePath
                );
                fileInfo.put("isFolder", file.isDirectory());
                if (file.isDirectory()) {
                    fileInfo.put("children", readFileList(file, filePath));
                }
                list.add(fileInfo);
            }
        }
        return list;
    }

    @NotAction
    private String fixPath(String path) {
        if (StringUtils.isBlank(path)) {
            return path;
        }
        if (path.indexOf("~") >= 0 || path.indexOf("..") >= 0) {
            throw new ParameterValidationException("路径不正确！");
        }
        path = path.replace("..", "").replace("//", "/");
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        return path;
    }

    /**
     * 文件下载接口
     *
     * @throws IOException
     */
    @NotAction
    public void download(GeneratorModel generatorModel) throws IOException {
        String path = fixPath(getPara("path"));
        File downFile = new File(CodeGenerator.getTemplateOutputFilePath(generatorModel) + path);
        if (!downFile.exists()) {
            renderJson(new Result(false, "找不到文件！"));
            return;
        }
        render(new Render() {
            private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream;";

            @Override
            public void render() {
                response.setHeader("Accept-Ranges", "bytes");
                //String newReportName  = reportName == null?"安全分析统计报告":reportName;
                String downloadFileNameHeader = ResponseUtils.getDownloadFileNameHeader(getRequest(), downFile.getName());
                response.setHeader("Content-disposition", downloadFileNameHeader);
                ServletOutputStream outputStream = null;
                InputStream fis = null;
                try {
                    outputStream = response.getOutputStream();
                    fis = new BufferedInputStream(new FileInputStream(downFile));
                    byte[] cache = new byte[1024];
                    int count = 0;
                    while ((count = fis.read(cache)) != -1) {
                        outputStream.write(cache, 0, count);//将缓冲区的数据输出到浏览器
                    }
                    outputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (outputStream != null) {
                        try {
                            outputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (fis != null) {
                        try {
                            fis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

        });
    }

    public void generateCode() {
        Result result = new Result(false);
        GeneratorModel generatorModel = GeneratorModel.dao.findById(getPara("gid"));
        try {
            CodeGenerator.generator(generatorModel);
            result.setSuccess(true);
        } catch (IOException e) {
            logger.error("生成代码异常！", e);
            LogWebSocketService.sendError(e);
        } catch (Exception e) {
            logger.error("生成代码异常！", e);
            LogWebSocketService.sendError(e);
        }
        renderJson(result);
    }

    /**
     * 预览文件
     */
    public void previewFile() {
        String gid = getPara("gid");
        String path = getPara("path");
        String charset = getPara("charset");
        Result result = new Result(false, "未知错误！");
        if (path == null || gid == null || path.trim().length() <= 0 || gid.trim().length() <= 0) {
            result.setMessage("无效的预览参数！");
            renderJson(result);
            return;
        }
        if (path != null && path.matches("\\.{2,}") || (gid != null && gid.matches("\\.{2,}"))) {
            result.setMessage("无效的文件路径！");
            renderJson(result);
            return;
        }
        String previewFile = CodeGenerator.getCodeOutputPath(GeneratorModel.dao.findById(gid)) + path;
        String fileContent = null;
        try {
            File file = new File(previewFile);
            fileContent = FileUtils.readFileToString(file, StringUtils.isBlank(charset) ? "UTF-8" : charset);
            result.setSuccess(true);
            result.setMessage("读取成功！");
            result.setData(fileContent);
        } catch (IOException e) {
            result.setSuccess(false);
            result.setMessage("文件读取IO异常！");
            logger.error("文件预览异常！", e);
        }
        renderJson(result);
    }

    public void download() {
        GeneratorModel generatorModel = GeneratorModel.dao.findById(getPara("generator_id"));
        File zip = ZipUtil.zip(CodeGenerator.getCodeOutputPath(generatorModel));
        render(new FileRender(zip, generatorModel.getGeneratorName() + ".zip"));
    }


    /**
     * 代码生成页面
     */
    public void codePage() {
        GeneratorModel generatorModel = GeneratorModel.dao.findById(getPara("generator_id"));
        String port = getPara("port");
        setAttr("hasStartedWeb", WebServerService.hasStartedWeb(Integer.parseInt(port)));
        setAttr("generatorModel", generatorModel);
        render("code.html");
    }

    /**
     * 代码编译
     */
    public void compile() {
        String gid = getPara("gid");
        GeneratorModel generatorModel = GeneratorModel.dao.findById(gid);
        CodeGenerator.compileCode(generatorModel);
        renderJson(new Result(true, "编译完成！"));
        return;
    }

    public void startWeb() {
        String gid = getPara("gid");
        GeneratorModel generatorModel = GeneratorModel.dao.findById(gid);
        try {
            int port = CodeGenerator.startTestWeb(generatorModel, getParaToBoolean("debug"));
            renderJson(new Result(true, "正在启动Web[:" + port + "]服务！").set("port", port));
        } catch (Exception exception) {
            logger.error("启动Web服务异常！", exception);
            renderJson(new Result(false, "启动Web服务异常！" + exception.getMessage()));
        }
    }


    public void stopWeb() {
//        String gid = getPara("gid");
        String port = getPara("port");
//        GeneratorModel generatorModel = GeneratorModel.dao.findById(gid);
        try {
            CodeGenerator.stopTestWeb(Integer.parseInt(port));
            renderJson(new Result(true, "停止Web[:" + port + "]服务成功！"));
        } catch (Exception exception) {
            exception.printStackTrace();
            logger.error("停止Web[:" + port + "]服务异常！", exception);
            renderJson(new Result(false, "停止Web[:" + port + "]服务异常！" + exception.getMessage()));
        }
    }

    /**
     * 部署代码
     */
    @RequiresPermissions("system:generator:code:deploy")
    public void deploy() {
        JwebSecurityAccount account = JwebSecurityUtils.getAccount();
        SysUser sysUser = SysUser.dao.findById(account.getUid());
        String gid = getPara("gid");
        Boolean isDeployMenu = getParaToBoolean("isDeployMenu");
        String pMenuId = getPara("pMenuId");
        GeneratorModel generatorModel = GeneratorModel.dao.findById(gid);
        try {
            // 1. 部署代码
            CodeGenerator.deployCode(generatorModel);
            if (!Boolean.FALSE.equals(isDeployMenu)) {
                // 2. 处理菜单权限
                processMenuPermission(sysUser, pMenuId, generatorModel);
            }
            renderJson(new Result(true, "部署代码成功！"));
        } catch (Exception exception) {
            logger.error("部署代码异常！", exception);
            renderJson(new Result(false, "部署代码异常," + exception.getMessage()));
        }
    }

    /**
     * 处理菜单权限
     *
     * @param sysUser
     * @param pMenuId
     * @param generatorModel
     */
    @NotAction
    private boolean processMenuPermission(SysUser sysUser, String pMenuId, GeneratorModel generatorModel) {
        LogWebSocketService.sendMessage("准备配置菜单权限...");
        String gid = String.valueOf(generatorModel.getGeneratorId());
        // 获取菜单权限关键字
        // 读取模板配置文件
        TemplateConfig templateConfig = null;
        try {
            String packagePath = GeneratorUtils.generatPackagePath(generatorModel.toGeneratorConfig().getPackagePath());
            ISource config = WebRootResourceFactory.me().getSource("/WEB-INF/views" + packagePath + "/gid_" + generatorModel.getGeneratorId() + "/_config.json", "UTF-8");
            if (config != null) {
                templateConfig = new Gson().fromJson( config.getContent().toString(), TemplateConfig.class);
            }
        } catch (JsonSyntaxException e) {
            throw new RuntimeException("解析模板配置文件_config.json异常！", e);
        }
        String checkMenuPermissionKey = "generator:" + gid + ":view";
        if (templateConfig != null && templateConfig.getPermissions()!=null && !templateConfig.getPermissions().isEmpty()) {
            checkMenuPermissionKey = "generator:" + gid + ":" + templateConfig.getPermissions().get(0).getKey();
        }
        String finalCheckMenuPermissionKey = checkMenuPermissionKey;
        TemplateConfig finalTemplateConfig = templateConfig;
        return Db.tx(() -> {
            String ctlUri = generatorModel.toGeneratorConfig().getCtlUri();
            List<SysOper> sysOpers = SysOper.dao.find("select * from sys_oper where permission_key = ?", finalCheckMenuPermissionKey);
            if (sysOpers == null || sysOpers.isEmpty()) {
                if (finalTemplateConfig != null && finalTemplateConfig.getPermissions()!=null && !finalTemplateConfig.getPermissions().isEmpty()) {
                    // 根据配置菜单信息创建菜单
                    sysOpers = createNewSysOpers(finalTemplateConfig.getPermissions(), 0, gid);
                } else {
                    // 创建默认菜单权限列表
                    sysOpers = createDefaultSysOpers(generatorModel, gid, finalCheckMenuPermissionKey, ctlUri);
                }
            }
            // 判断是否有权限？
            if (!JwebSecurityUtils.getSession().isPermitted(finalCheckMenuPermissionKey) && !isPermittedMenuSyncDb(finalCheckMenuPermissionKey)) {
                // 给当前用户授权
                SysRole sysRole = SysRole.dao.findFirst("select r.* from sys_role r, sys_role_user ru where r.role_id = ru.role_id and ru.user_id =  ? limit 1", sysUser.getUserId());
                if (sysRole == null) {
                    // 创建个人专用角色
                    sysRole = new SysRole();
                    sysRole.setRoleKey(sysUser.getUserAccount() + "_" + sysUser.getUserId());
                    sysRole.setRoleName(sysUser.getUserName() + "专用角色");
                    sysRole.setRolePid(0);
                    sysRole.setCreateDatetime(new Date());
                    sysRole.save();
                    // 人员绑定角色
                    SysRoleUser sysRoleUser = new SysRoleUser();
                    sysRoleUser.setUserId(sysUser.getUserId());
                    sysRoleUser.setRoleId(sysRole.getRoleId());
                    sysRoleUser.save();
                    LogWebSocketService.sendMessage("创建角色《" + sysUser.getUserName() + "专用角色" + "》");
                }
                // 角色授权
                for (SysOper sysOper : sysOpers) {
                    SysRoleOper sro = new SysRoleOper();
                    sro.setRoId(IDGenerator.nextId("SysRoleOper", IDGenerator.TYPE_SNOWFLAKE));
                    sro.setOperId(sysOper.getOperId());
                    sro.setRoleId(sysRole.getRoleId());
                    sro.save();
                }
                LogWebSocketService.sendMessage("完成菜单权限授权！");
            }
            LogWebSocketService.sendMessage("配置菜单权限完成！");
            return true;
        });
    }

    /**
     * 创建新的操作权限
     *
     * @param permissions
     * @param pOperId
     * @param gid
     * @return
     */
    @NotAction
    private List<SysOper> createNewSysOpers(List<PermissionVo> permissions, int pOperId, String gid) {
        List<SysOper> sysOpers = new ArrayList<>(permissions.size());
        int order = 0;
        for (PermissionVo permission : permissions) {
            SysOper sysOper = new SysOper();
            sysOper.setOperPid(pOperId);
            sysOper.setCreateDatetime(new Date());
            sysOper.setOperKey(permission.getKey());
            sysOper.setOperName(permission.getTitle());
            boolean isMenu = "menu".equals(permission.getType());
            sysOper.setOperType(isMenu ? 0 : 1);
            sysOper.setOrderNo(order++);
            sysOper.setPermissionKey("generator:" + gid + ":" + permission.getKey());
            sysOper.setStatus(true);
            sysOper.setOperIcon(permission.getIcon());
            sysOper.setOperPath(permission.getPath());
            sysOper.save();
            sysOpers.add(sysOper);
            LogWebSocketService.sendMessage("创建"+(isMenu ?"菜单":"权限")+"《" + sysOper.getOperName() + "》");
            if (permission.getChildren() != null) {
                sysOpers.addAll(createNewSysOpers(permission.getChildren(), sysOper.getOperId(), gid));
            }
        }
        return sysOpers;
    }

    /**
     * 创建默认菜单权限列表
     *
     * @param generatorModel
     * @param gid
     * @param checkMenuPermissionKey
     * @param ctlUri
     * @return
     */
    @NotAction
    private List<SysOper> createDefaultSysOpers(GeneratorModel generatorModel, String gid, String checkMenuPermissionKey, String ctlUri) {
        List<SysOper> sysOpers = new ArrayList<>();
        // 创建菜单
        SysOper menu = new SysOper();
        menu.setOperKey(gid);
        menu.setOperName(generatorModel.getGeneratorName());
        menu.setOperPath(ctlUri);
        menu.setOperPid(0);
        menu.setOperType(0);
        menu.setOrderNo(0);
        menu.setOperIcon("layui-icon-rate-solid");
        menu.setPermissionKey(checkMenuPermissionKey);
        menu.setStatus(true);
        menu.setCreateDatetime(new Date());
        menu.save();
        LogWebSocketService.sendMessage("创建菜单《" + generatorModel.getGeneratorName() + "》");
        sysOpers.add(menu);

        // 创建其它权限
        // list, add, edit. del
        String[] otherOper = new String[]{"list", "add", "edit", "del"};
        String[] otherOperName = new String[]{"查询", "新增", "编辑", "删除"};
        int idx = 0;
        for (String operKey : otherOper) {
            // 创建权限。
            SysOper oper = new SysOper();
            oper.setOperKey(operKey);
            oper.setOperName(otherOperName[idx]);
            oper.setOperPid(menu.getOperId());
            oper.setOperType(1);
            oper.setOrderNo(idx);
            oper.setPermissionKey("generator:" + gid + ":" + operKey);
            oper.setStatus(true);
            oper.setCreateDatetime(new Date());
            oper.save();
            LogWebSocketService.sendMessage("创建权限《" + otherOperName[idx] + "》");
            sysOpers.add(oper);
            idx++;
        }
        return sysOpers;
    }

    /**
     * 从数据库中判断是否有指定菜单的权限
     *
     * @param menuPermissionKey
     * @return
     */
    private boolean isPermittedMenuSyncDb(String menuPermissionKey) {
        String uid = JwebSecurityUtils.getAccount().getUid();
        Integer count = Db.queryInt("select count(1) from sys_role_oper sro, sys_role_user sru, sys_oper so where sru.role_id = sro.role_id and sru.user_id = ? and sro.oper_id = so.oper_id and so.permission_key = ?", uid, menuPermissionKey);
        return count > 0;
    }
}